// threadtest.cc 
//	Simple test case for the threads assignment.
//
//	Create two threads, and have them context switch
//	back and forth between themselves by calling Thread::Yield, 
//	to illustratethe inner workings of the thread system.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "system.h"
#include "synch.h"

Semaphore semNempty("empty", 6);
Semaphore semNfull("full", 0);
Semaphore sempool[6];
bool pool[6] = {0};

Semaphore mutex("count", 1);
Semaphore customer("customer", 0);
Semaphore barber("barber", 0);

Barrier barrier("barrier", 3);

rwLock rwlock("rwlock");
int share = 0;

Lock lock("mutex");
Condition condp("condp"), condc("condc");
int count = 0;


int customer_count = 0;

// testnum is set in main.cc
int testnum = 1;

//----------------------------------------------------------------------
// SimpleThread
// 	Loop 5 times, yielding the CPU to another ready thread 
//	each iteration.
//
//	"which" is simply a number identifying the thread, for debugging
//	purposes.
//----------------------------------------------------------------------

Lock *lock0;
Lock *lock1;
Lock *lock2;

void
DeadLockTest(int which)
{
    if (which == 1) {
        printf("Thread 1 Wait For Lock 0\n"); 
        lock0->Acquire();
        printf("Thread 1 Acquire Lock 0\n");
        currentThread->Yield();
        printf("Thread 1 Wait For Lock 1\n");
        lock1->Acquire();
        printf("Thread 1 Acquire Lock 1\n");
    } else if (which == 2) {
        printf("Thread 2 Wait For Lock 1\n");
        lock1->Acquire();
        printf("Thread 2 Acquire Lock 1\n");
        currentThread->Yield();
        printf("Thread 2 Wait For Lock 2\n");
        lock2->Acquire();
        printf("Thread 2 Acquire Lock 2\n");  
    } else if (which == 3) {
        printf("Thread 3 Wait For Lock 2\n");
        lock2->Acquire();
        printf("Thread 3 Acquire Lock 2\n");
        currentThread->Yield();
        printf("Thread 3 Wait For Lock 0\n");
        lock0->Acquire();
        printf("Thread 3 Acquire Lock 0\n");
    } else {
        for (int i = 0; i < 10; i++) {
            int ret = CheckDeadLock();
            if (ret == -1) printf("No Dead Lock!\n");
            currentThread->Yield();
        }
    }
}

void 
MsgTest(int which)
{
    int i;
    char send[64]="aaaaaa";
    if (which == 2) send[1] = 'b';
    else if (which == 3) send[1] = 'c';
    for (i = 0; i < 5; i++)
    {
        char recv[64] = "\0";
        send[3] = 'a'+i;
        int tid = currentThread->RecvMsg(recv);
        if (tid == -1) {
            printf("%d cannot recv a msg\n", currentThread->getThreadid());
        }
        else {
            printf("%d recv msg \"%s\" from %d\n", currentThread->getThreadid(), recv, tid);
        }
        tid = currentThread->getThreadid()+1;
        int ret = currentThread->SendMsg(tid, send);
        if (ret == 1) {
            printf("%d send msg \"%s\" to %d\n", currentThread->getThreadid(), send, tid);
        }
        else {
            printf("%d cannot send a msg to %d\n", currentThread->getThreadid(), tid);
        }
    }
}

void
SimpleThread(int which)
{
    int num;
    
    for (num = 0; num < 5; num++) {
        printf("Thread id : %d ; Thread name : %s ; Priority : %d ; looped %d times\n", currentThread->getThreadid(), currentThread->getName(), currentThread->getPriority(), num);
	//printf("*** thread %d looped %d times\n", which, num);
        currentThread->Yield();
    }
}

void Produce(int arg)
{
    while (1) {
        semNempty.P();
        int i,j;
        for (i = 0; i < 6; i++) {
            sempool[i].P();
            if (!pool[i]) {
                pool[i] = true;
                printf("%s produces %d |", currentThread->getName(), i);
                for (j = 0; j < 6; j++)
                    printf(" %d",pool[j]);
                printf("\n");
                sempool[i].V();
                currentThread->Yield();
                break;
            }
            else {
                sempool[i].V();
            }
        }
        semNfull.V();
    }
}

void Consume(int arg)
{
    while (1) {
        semNfull.P();
        int i,j;
        for (i = 0; i < 6; i++) {
            sempool[i].P();
            if (pool[i]) {
                pool[i] = false;
                printf("%s consumes %d |", currentThread->getName(), i);
                for (j = 0; j < 6; j++)
                    printf(" %d",pool[j]);
                printf("\n");
                sempool[i].V();
                currentThread->Yield();
                break;
            }
            else {
                sempool[i].V();
            }
        }
        semNempty.V();
    }
}

void cond_Produce(int arg)
{
    while (1) {
        int i,j;
        for (i = 0; i < 6; i++) {
            lock.Acquire();
            if (count == 6) {
                condp.Wait(&lock);
            }
            else {
                count++;
                printf("%s produces a product. There are %d products.\n", currentThread->getName(), count);
            }
            condc.Broadcast(&lock);
            lock.Release();
        }
    }
}

void cond_Consume(int arg)
{
    while (1) {
        int i,j;
        for (i = 0; i < 6; i++) {
            lock.Acquire();
            if (count == 0) {
                condc.Wait(&lock);
            }
            else {
                count--;
                printf("%s consumes a product. There are %d products.\n", currentThread->getName(), count);
            }
            condp.Broadcast(&lock);
            lock.Release();
        }
    }
}

void func_barber(int arg)
{
    while (1) {
        customer.P();
        mutex.P();
        customer_count--;
        printf("Barber works! %d customers are waiting.\n", customer_count);
        mutex.V();
        barber.V();
        currentThread->Yield();
    }
}

void func_customer(int arg)
{
    while (1) {
        mutex.P();
        if (customer_count < 3) {
            customer_count++;
            printf("Customer Thread %d : Customer comes in. %d customers are waiting.\n", arg, customer_count);
            mutex.V();
            customer.V();
            barber.P();
        }
        else {
            printf("Customer Thread %d : Customer leaves without having hair cut.\n", arg);
            mutex.V();
        }
        //currentThread->Yield();
    }
}

void TestBarrier(int arg)
{
    printf("%s waits for barrier.\n", currentThread->getName());
    barrier.Wait();
    printf("%s is running.\n", currentThread->getName());
}

void Reader(int arg)
{
    while (1) {
        printf("%s waits for rwlock\n", currentThread->getName());
        rwlock.readlock();
        printf("%s is reading , share = %d\n", currentThread->getName(), share);
        rwlock.unlock();
    }
}

void Writer(int arg)
{
    while (1) {
        printf("%s waits for rwlock\n", currentThread->getName());
        rwlock.writelock();
        share++;
        printf("%s is writing , share = %d\n", currentThread->getName(), share);
        rwlock.unlock();
    }
}

//----------------------------------------------------------------------
// ThreadTest1
// 	Set up a ping-pong between two threads, by forking a thread 
//	to call SimpleThread, and then calling SimpleThread ourselves.
//----------------------------------------------------------------------

void
ThreadTest1()
{
    DEBUG('t', "Entering ThreadTest1");
/*
    lock0 = new Lock("lock0");
    lock1 = new Lock("lock1");
    lock2 = new Lock("lock2");

    Thread *t1 = new Thread("T1", 1);
    Thread *t2 = new Thread("T2", 1);
    Thread *t3 = new Thread("T3", 1);
    Thread *t4 = new Thread("T4", 1);

    t1->Fork(DeadLockTest, 1);
    t2->Fork(DeadLockTest, 2);
    t3->Fork(DeadLockTest, 3);
    t4->Fork(DeadLockTest, 4);

 /*   
    Thread *t1 = new Thread("T1", 1);
    Thread *t2 = new Thread("T2", 1);
    Thread *t3 = new Thread("T3", 1);

    t1->Fork(MsgTest, 1);
    t2->Fork(MsgTest, 2);
    t3->Fork(MsgTest, 3);*/
//    Thread *t = new Thread("forked thread");

//    t->Fork(SimpleThread, 1);
//    SimpleThread(0);

/*    Thread *t1 = new Thread("Thread A", 1);
    Thread *t2 = new Thread("Thread B", 2);
    Thread *t3 = new Thread("Thread C", 1);
    Thread *t4 = new Thread("Thread D", 2);

    t1->Fork(SimpleThread, 0);
    t2->Fork(SimpleThread, 0);
    t3->Fork(SimpleThread, 0);
    t4->Fork(SimpleThread, 0);*/

/*    Thread *producer[5], *consumer[3];

    producer[0] = new Thread("Producer 0", 1);
    producer[1] = new Thread("Producer 1", 1);
    producer[2] = new Thread("Producer 2", 1);
    producer[3] = new Thread("Producer 3", 1);
    producer[4] = new Thread("Producer 4", 1);
    consumer[0] = new Thread("Consumer 0", 1);
    consumer[1] = new Thread("Consumer 1", 1);
    consumer[2] = new Thread("Consumer 2", 1);

 /*   for (int i = 0; i < 5; i++) {
        producer[i]->Fork(cond_Produce, 0);
    }

    for (int i = 0; i < 3; i++) {
        consumer[i]->Fork(cond_Consume, 0);
    }

    for (int i = 0; i < 5; i++) {
        producer[i]->Fork(Produce, 0);
    }

    for (int i = 0; i < 3; i++) {
        consumer[i]->Fork(Consume, 0);
    }

 /*   Thread *thread_barber = new Thread("Barber", 1);
    Thread *thread_customer[5];

    int i;
    for (i = 0; i < 5; i++)
        thread_customer[i] = new Thread("Customer", 1);

    thread_barber->Fork(func_barber, 0);
    for (i = 0; i < 5; i++)
        thread_customer[i]->Fork(func_customer, i);*/

/*    Thread *thread[4];
    thread[0] = new Thread("Thread A", 2);
    thread[1] = new Thread("Thread B", 2);
    thread[2] = new Thread("Thread C", 2);
    thread[3] = new Thread("Thread D", 2);

    for (int i = 0; i < 4; i++)
        thread[i]->Fork(TestBarrier, i);
/*
    Thread *reader[4], *writer[2];
    int i;

    reader[0] = new Thread("Reader A", 2);
    reader[1] = new Thread("Reader B", 2);
    reader[2] = new Thread("Reader C", 2);
    reader[3] = new Thread("Reader D", 2);
    writer[0] = new Thread("Writer E", 2);
    writer[1] = new Thread("Writer F", 2);

    for (i = 0; i < 4; i++)
        reader[i]->Fork(Reader, i);
    for (i = 0; i < 2; i++)
        writer[i]->Fork(Writer, i);*/
}

//----------------------------------------------------------------------
// ThreadTest
// 	Invoke a test routine.
//----------------------------------------------------------------------

void
ThreadTest()
{
    switch (testnum) {
    case 1:
	ThreadTest1();
	break;
    default:
	printf("No test specified.\n");
	break;
    }
}

